﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace ES.Migrate
{
    public class MigrateWorker2<T>
    {
        /// <summary>
        /// 数据包大小(默认16mb)
        /// </summary>
        public static long PackSize { get; set; } = 1024 * 1024 * 16;

        /// <summary>
        /// 标记 PackSize索引 的补齐位数
        /// 最大文件Size = 16 * 999,999
        /// </summary>
        public static readonly int PadLength = 6;

        /// <summary>
        /// 字符串的标记长度
        /// </summary>
        public static int MarkLength
        {
            get
            {
                return PadLength * 2 + 1;
            }
        }

        public event EventHandler<OnStartEventArgs<T>> OnStart;

        public event EventHandler<OnUpdateProgressEventArgs<T>> OnUpdateProgress;

        public event EventHandler<OnErrorEventArgs<T>> OnError;

        public event EventHandler<OnMd5CheckingEventArgs<T>> OnMd5Checking;

        public event EventHandler<OnCompletedEventArgs<T>> OnCompleted;

        #region 属性

        /// <summary>
        /// 工作状态
        /// </summary>
        public WorkState State { get; private set; }

        /// <summary>
        /// 文件源路径
        /// </summary>
        public FileInfo SrcFileInfo { get; private set; }

        /// <summary>
        /// 文件的目标路径
        /// </summary>
        public FileInfo TgtFileInfo { get; private set; }


        /// <summary>
        /// 文件的目标目录
        /// </summary>
        public string TgtDir { get; private set; }

        /// <summary>
        /// 文件大小
        /// </summary>
        public long FileLength { get; private set; }

        /// <summary>
        /// 需传输包的次数
        /// </summary>
        public int PackCount { get; private set; }

        /// <summary>
        /// 总进度：默认 PackCount*2
        /// 分片占100，合并分片占100,校验文件占1
        /// </summary>
        public int MaxProgress { get; private set; }


        /// <summary>
        /// 进度百分比
        /// </summary>
        public decimal CurrRate
        {
            get
            {
                var re = Math.Round(((this.CurrPosIndex + 1) * 1.000 / this.MaxProgress * 1.000), 3);
                return (decimal)(re > 1 ? 1 : re);
            }
        }

        public T UserState
        {
            get;
            set;
        }

        public bool Md5Check { get; private set; }

        /// <summary>
        /// 是否是拷贝？
        /// true：拷贝，源文件会保留
        /// false:迁移，源文件会删除
        /// </summary>
        public bool IsCopy { get; set; }

        /// <summary>
        /// 是否忙碌(正在执行)
        /// </summary>
        public bool IsBusy { set; private get; }

        /// <summary>
        /// 执行命令
        /// </summary>
        private ConcurrentQueue<WorkState> QueState { set; get; } = new ConcurrentQueue<WorkState>();

        #endregion


        #region 构造函数

        public MigrateWorker2(T userState, string srcPath, string tgtPath, bool isCopy = true, bool md5Check = true)
        {
            this.UserState = userState;
            this.SrcFileInfo = new FileInfo(srcPath);
            this.TgtFileInfo = new FileInfo(tgtPath);
            this.IsCopy = isCopy;
            this.Md5Check = md5Check;
            this.State = WorkState.等待;

            Init();
        }

        private void Init()
        {
            if (!this.SrcFileInfo.Exists)
            {
                throw new ArgumentException("文件不存在！", "srcPath");
            }

            this.TgtDir = this.TgtFileInfo.DirectoryName;

            if (!Directory.Exists(this.TgtDir))
            {
                Directory.CreateDirectory(this.TgtDir);
            }

            this.FileLength = this.SrcFileInfo.Length;

            if ((this.FileLength % MigrateWorker2<T>.PackSize) > 0)
            {
                this.PackCount = (int)(this.FileLength / MigrateWorker2<T>.PackSize) + 1;
            }
            else
            {
                this.PackCount = (int)(this.FileLength / MigrateWorker2<T>.PackSize);
            }

            // 最大进度 = 分配的包总个数 + Md5分配
            this.MaxProgress = this.PackCount * 1;
        }

        #endregion

        /// <summary>
        /// 开始执行
        /// </summary>
        public void Start()
        {
            this.OnStart?.Invoke(this, new OnStartEventArgs<T>(this.UserState));
            RunWorkerAsync();
        }

        /// <summary>
        /// 取消
        /// </summary>
        public void CancelAsync()
        {
            if (this.IsBusy)
            {
                this.State = WorkState.取消;
            }
            else
            {
                DeleteOver();
                this._CurrPosIndex = 0;
                this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
            }
        }

        /// <summary>
        /// 暂停
        /// </summary>
        public void PauseAsync()
        {
            this.State = WorkState.暂停;
        }

        /// <summary>
        /// 继续
        /// </summary>
        public void ContinceAsync()
        {
            if (this.IsBusy)
            {
                this.QueState.Enqueue(WorkState.继续);
            }
            else
            {
                this.State = WorkState.继续;
                if (this.CurrRate >= 1) // this.CurrRate 已经走 CurrPosIndex 执行流程，也通过Md5检验，则完成
                {
                    this.State = WorkState.完成;
                    if (!this.IsCopy)
                    {
                        SrcFileInfo.Delete();
                    }
                    this.OnCompleted?.Invoke(this, new OnCompletedEventArgs<T>(this.UserState, true));
                }
                else
                {
                    this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                    RunWorkerAsync();
                }
            }
        }

        /// <summary>
        /// 重做
        /// </summary>
        public void RedoAsync()
        {
            if (this.IsBusy)
            {
                this.QueState.Enqueue(WorkState.重做);
            }
            else
            {
                this.State = WorkState.重做;
                if (this.CurrRate >= 1) // this.CurrRate 已经走 CurrPosIndex 执行流程，也通过Md5检验，则完成
                {
                    this.State = WorkState.完成;
                    if (!this.IsCopy)
                    {
                        SrcFileInfo.Delete();
                    }
                    this.OnCompleted?.Invoke(this, new OnCompletedEventArgs<T>(this.UserState, true));
                }
                else
                {
                    DeleteOver();
                    this._CurrPosIndex = 0;
                    this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                    RunWorkerAsync();
                }
            }
        }

        
        private string GeneratePos(object index)
        {
            string res = index.ToString().PadLeft(PadLength, '0') + "-" + this.PackCount.ToString().PadLeft(PadLength, '0');
            return res;
        }

        private long _CurrPosIndex = 0;
        public long CurrPosIndex
        {
            get
            {
                if (_CurrPosIndex == 0)
                {   
                    if (this.TgtFileInfo.Exists)
                    {
                        if (TgtFileInfo.Length > 0)
                        {
                            if (TgtFileInfo.Length == SrcFileInfo.Length)//md5 比对文件，文件一致则跳过
                            {
                                //if (Md5Compare()) //下面会对 md5 检验
                                {
                                    _CurrPosIndex = this.PackCount;
                                }
                                //else //文件 虽然大小一致，md5 不一样 ， 则从头开始写入
                                //{
                                //    _CurrPosIndex = 0;
                                //    return _CurrPosIndex;
                                //}
                            }
                            else
                            {
                                if (TgtFileInfo.Length > SrcFileInfo.Length)//待迁移或待拷贝的 文件 比源文件 还大， 则从头开始写入
                                {
                                    _CurrPosIndex = 0;
                                    return _CurrPosIndex;
                                }

                                FileStream fs = null;
                                try
                                {
                                    fs = new FileStream(this.TgtFileInfo.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                                    if (fs.Length <= 0)
                                    {
                                        _CurrPosIndex = 0;
                                        return _CurrPosIndex;
                                    }
                                    fs.Position = fs.Length - MarkLength;
                                    var buffer = new byte[MarkLength];
                                    fs.Read(buffer, 0, buffer.Length);
                                    string str = Encoding.Default.GetString(buffer);
                                    var arr = str.Split('-');
                                    if (arr.Length == 2)
                                    {
                                        long totalPackCount = 0;
                                        //这里验证 包的总个数 是否一致，如果不一致，则表示是不可用的 数据
                                        if (long.TryParse(arr[1], out totalPackCount) && totalPackCount == this.PackCount)
                                        {
                                            long.TryParse(arr[0], out _CurrPosIndex);
                                        }
                                    }
                                }
                                catch(Exception ex)
                                {
                                    _CurrPosIndex = 0;
                                    return _CurrPosIndex;
                                }
                                finally
                                {
                                    fs?.Close();
                                    fs?.Dispose();
                                }
                            }
                        }
                    }
                }
                return _CurrPosIndex;
            }
            set
            {
                _CurrPosIndex = value;
            }
        }
        private object locker = new object();
        private void RunWorkerAsync()
        {
            lock (locker)
            {
                Task.Run(() =>
                {
                    this.State = WorkState.执行中;
                    this.IsBusy = true;
                    try
                    {
                        using (FileStream srcFS = new FileStream(this.SrcFileInfo.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                        {
                            using (FileStream tgtFS = new FileStream(this.TgtFileInfo.FullName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
                            {
                                for (; this.CurrPosIndex < this.PackCount; this._CurrPosIndex++)
                                {
                                    //本次设定 文件尾 分片位置信息
                                    var posBuff = Encoding.Default.GetBytes(GeneratePos(this._CurrPosIndex));

                                    //本次 传递的 分片数据
                                    int bfLength = (int)Math.Min(PackSize, this.FileLength - this._CurrPosIndex * PackSize);

                                    //写入的数据 = 分片数据 + 分片位置信息
                                    byte[] buffer = new byte[bfLength + posBuff.Length];

                                    //从位置信息 开始
                                    srcFS.Position = _CurrPosIndex * PackSize;

                                    //读取时 只读分片数据
                                    srcFS.Read(buffer, 0, bfLength);

                                    //将 分片位置信息 填充到 同一个字节数组buffer
                                    Buffer.BlockCopy(posBuff, 0, buffer, bfLength, posBuff.Length);

                                    //从什么位置读，就从什么位置写，不过尾部会增加 分片位置信息（每次写入会替换上一次写入的 分片位置信息）
                                    tgtFS.Position = _CurrPosIndex * PackSize;

                                    //分片数据 + 分片位置信息 同时写入
                                    tgtFS.Write(buffer, 0, buffer.Length);

                                    //检测 暂停 或 取消
                                    if (this.State == WorkState.暂停 || this.State == WorkState.取消)
                                    {
                                        return;
                                    }
                                    else
                                    {
                                        //报告进度
                                        this.OnUpdateProgress?.Invoke(this, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                                    }
                                }

                                //最后 1个 多出的  分片位置信息 将会截除掉
                                tgtFS.SetLength(srcFS.Length);
                            }
                        }

                        if (this.State != WorkState.暂停 && this.State != WorkState.取消)
                        {
                            bool? isOK = null;
                            if (this.Md5Check)
                            {
                                this.State = WorkState.校验文件;
                                this.OnMd5Checking?.Invoke(this, new OnMd5CheckingEventArgs<T>(this.UserState));
                                isOK = Md5Compare();
                            }
                            
                            this.State = WorkState.完成;
                            if (!this.IsCopy)
                            {
                                SrcFileInfo.Delete();
                            }
                            this.OnCompleted?.Invoke(this, new OnCompletedEventArgs<T>(this.UserState, isOK));
                        }

                        //再判断1次，在检验文件时可能 点其他 按钮
                        if (this.State == WorkState.取消)
                        {
                            DeleteOver();
                            this._CurrPosIndex = 0;
                            this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                        }
                        else if (this.State == WorkState.暂停)
                        {

                        }

                        while (true)
                        {
                            if (this.QueState.Count > 0)
                            {
                                WorkState wks = WorkState.完成;
                                #region 尝试获取状态
                                while (true)
                                {
                                    if (!this.QueState.TryDequeue(out wks))
                                    {
                                        Thread.Sleep(500);
                                    }
                                    else
                                    {
                                        break;
                                    }
                                }
                                #endregion
                                this.State = wks;

                                if (this.State == WorkState.继续)
                                {
                                    this.State = WorkState.继续;                                
                                    this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                                    RunWorkerAsync();
                                }
                                else if (this.State == WorkState.重做)
                                {
                                    DeleteOver();
                                    this._CurrPosIndex = 0;
                                    this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                                    RunWorkerAsync();
                                }
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        this.OnError?.Invoke(this, new OnErrorEventArgs<T>(this.UserState, ex));
                    }
                    finally
                    {
                        this.IsBusy = false;
                    }
                });
            }
        }

        #region private 方法

     
        /// <summary>
        /// 删除目标文件
        /// </summary>
        private void DeleteOver()
        {
            if (TgtFileInfo.Exists)
            {
                TgtFileInfo.Delete();
            }
        }


        /// <summary>
        /// md5比对文件
        /// </summary>
        /// <returns></returns>
        private bool Md5Compare()
        {
            string md51 = FileMD5(this.SrcFileInfo.FullName);
            string md52 = FileMD5(this.TgtFileInfo.FullName);

            if (md51 == null || md52 == null)
            {
                return false;
            }
            return md51.Equals(md52);
        }


        /// <summary>
        /// 计算文件的Md5
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private static string FileMD5(string path)
        {
            if (!File.Exists(path))
            {
                return null;
            }
            int bufferSize = 1024 * 16;
            byte[] buffer = new byte[bufferSize];
            Stream inputStream = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
            HashAlgorithm hashAlgorithm = new MD5CryptoServiceProvider();
            int readLength = 0;//每次读取长度
            var output = new byte[bufferSize];
            while ((readLength = inputStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                //计算MD5
                hashAlgorithm.TransformBlock(buffer, 0, readLength, output, 0);
            }
            //完成最后计算，必须调用(由于上一部循环已经完成所有运算，所以调用此方法时后面的两个参数都为0)
            hashAlgorithm.TransformFinalBlock(buffer, 0, 0);
            string md5 = BitConverter.ToString(hashAlgorithm.Hash).Replace("-", "");
            hashAlgorithm.Clear();
            inputStream.Close();
            inputStream.Dispose();
            return md5;
        }

        #endregion
    }
}
